package br.cin.ufpe.feedbacktest.core;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.TreeMap;
import java.util.Vector;


public class RandomResolver {

	private static Class<?>[] comparableClasses = new Class<?>[]{
		String.class,
		long.class,
		byte.class,
		int.class,
		short.class,
		char.class,
		double.class,
		boolean.class,
		float.class
	};

	private static Class<?>[] collectionClasses = new Class<?>[]{
		Vector.class,
		ArrayList.class,
		HashMap.class,
		TreeMap.class,
		HashSet.class		
	};

	public static Variable getRandomVariable(Class<?> parameterClass, Sequence[] preExtendSequences, boolean toNameObjectFlag) {		
		Variable randomVariable = new Variable();

		if (parameterClass.isPrimitive() || parameterClass.getName().equals(String.class.getName())) {

			randomVariable.setValue(instantiatePrimitiveClass(parameterClass));

		} 
		else if(parameterClass.equals(java.lang.Comparable.class)){

			randomVariable.setValue(instantiateComparableObject());

		}
		else {

			// Verifying restrictions
			// (interface and empty constructors)
			boolean restrictions = checkInstantiationRestrictions(parameterClass);

			// New Object
			// or Use Reference
			boolean randomBoolean = RandomFactory.randomBoolean();

			if (randomBoolean) {

				// New
				// Object

				if (!restrictions) {
					randomVariable = instantiateVariable(parameterClass, toNameObjectFlag);

				} else {
					Variable variable = reuseSomeVariable(parameterClass, preExtendSequences);
					if (variable != null) {
						randomVariable = variable;

					} else {
						randomVariable.setValue(null);
					}
				}

			} else {

				// Use
				// Reference

				Variable variable = reuseSomeVariable(parameterClass, preExtendSequences);				
				if (variable != null) {
					randomVariable = variable;

				} else if (!restrictions) {
					randomVariable = instantiateVariable(parameterClass, toNameObjectFlag);

				} else {
					randomVariable.setValue(null);
				}
			}
		}

		randomVariable.setType(parameterClass);
		return randomVariable;
	}

	private static Variable reuseSomeVariable(Class<?> parameterClass, Sequence[] preExtendSequences) {
		Variable variable = null;

		Vector<Variable> reusableVariables = new Vector<Variable>();
		for (Sequence successSequence:preExtendSequences) {			
			successSequence.checkReusableVariables(parameterClass, reusableVariables);
		}

		if (reusableVariables.size() > 0) {
			int randomIndex = RandomFactory.randomNumberBetween(0, reusableVariables.size());
			variable = reusableVariables.get(randomIndex);
		}

		return variable;
	}

	private static boolean checkInstantiationRestrictions(Class<?> parameterClass) {
		boolean restrictions = false;

		if ( (parameterClass.isInterface() || Modifier.isAbstract(parameterClass.getModifiers()) )
				&& !parameterClass.isArray()) {
			restrictions = true;

		} /*else {
			restrictions = true;
			Constructor<?>[] constructors = parameterClass.getConstructors();
			for (int i = 0; i < constructors.length; i++) {
				if (constructors[i].getParameterTypes().length == 0) {
					restrictions = false;
					break;
				}
			}
		}*/

		return restrictions;
	}

	private static Variable instantiateVariable(Class<?> parameterClass, boolean toNameObjectFlag) {
		Variable variable = new Variable();

		try {			
			Object o = instantiateClass(parameterClass,variable);			
			variable.setValue(o);

			if (toNameObjectFlag) {
				variable.setName(VariableNamer.nextName());
			}

		} catch (Exception e) {
			System.out.println("Error at " + parameterClass.getName() + " new Instance");
			e.printStackTrace(System.out);
		}

		return variable;
	}

	public static Object instantiateClass(Class<?> parameterClass, Variable variable) 
	throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		Object newObject = null;		

		Constructor<?>[] classConstructors = null;
		if(parameterClass.equals(String.class)){
			classConstructors = new Constructor<?>[]{parameterClass.getConstructors()[1]};
		}
		else if(isCompoundPrimitive(parameterClass)){			
			classConstructors = new Constructor<?>[]{parameterClass.getConstructors()[1]};
		}
		else if(parameterClass.equals(Collection.class)){
			classConstructors = new Constructor<?>[]{getCollectionConstructor()};
		}		
		else{
			classConstructors = parameterClass.getConstructors();
		}		

		if(classConstructors.length > 0){
			int randomIndex = RandomFactory.randomNumberBetween(0, classConstructors.length);
			Constructor<?> classConstructor = classConstructors[randomIndex];

			Class<?>[] constructorParameters = classConstructor.getParameterTypes();
			if(constructorParameters.length == 0){
				newObject = parameterClass.newInstance();
			}else{
				Object[] constructorParametersObjects = new Object[constructorParameters.length];
				int parametersIndex = 0;

				for(Class<?> parameterType:constructorParameters){

					if(parameterType.isPrimitive() || parameterType.getName().equals(String.class.getName())){
						constructorParametersObjects[parametersIndex] = instantiatePrimitiveClass(parameterType);
					}else{
						constructorParametersObjects[parametersIndex] = instantiateClass(parameterType,variable);
					}
					parametersIndex++;

				}

				variable.setParametersValues(constructorParametersObjects);
				variable.setParametersTypes(constructorParameters);

				newObject = classConstructor.newInstance(constructorParametersObjects);

			}			

		}else{
			if(parameterClass.isArray()){
				newObject = Array.newInstance(parameterClass.getComponentType(), 0);
			}
			else if(parameterClass.equals(Comparable.class)){
				newObject = instantiateComparableObject();
			}
			else{
				newObject = parameterClass.newInstance();
			}
		}

		return newObject;
	}



	/**
	 * This Method creates a instance of a primitive Class.
	 * 
	 */
	private static Object instantiatePrimitiveClass(Class<?> parameterClass){
		Object primitiveInstance = null;

		if (parameterClass.equals(byte.class)) {				
			byte b = (byte) RandomFactory.randomNumber();				
			primitiveInstance = b;

		} else if (parameterClass.equals(short.class)) {
			short s = (short) RandomFactory.randomNumber();				
			primitiveInstance = s;

		} else if (parameterClass.equals(int.class)) {
			int i = RandomFactory.randomNumber();				
			primitiveInstance = i;

		} else if (parameterClass.equals(long.class)) {
			long l = (long) RandomFactory.randomNumber();				
			primitiveInstance = l;

		} else if (parameterClass.equals(float.class)) {
			float f = (float) RandomFactory.randomDecimal();				
			primitiveInstance = f;

		} else if (parameterClass.equals(double.class)) {
			double d = RandomFactory.randomDecimal();				
			primitiveInstance = d;

		} else if (parameterClass.equals(char.class)) {
			char c = RandomFactory.randomChar();				
			primitiveInstance = c;

		} else if (parameterClass.equals(boolean.class)) {
			boolean b = RandomFactory.randomBoolean();				
			primitiveInstance = b;
		} else if (parameterClass.equals(String.class)) {
			String s = VariableNamer.nextConstructorVariable();				
			primitiveInstance = s;
		}

		return primitiveInstance;
	}

	private static Object instantiateComparableObject(){
		Object obj = null;

		int index = RandomFactory.randomNumberBetween(0, comparableClasses.length);
		Class<?> comparableClass = comparableClasses[index];

		obj = instantiatePrimitiveClass(comparableClass);		

		return obj;
	}

	private static Constructor<?> getCollectionConstructor(){
		Constructor<?> constructor = null;

		int index = RandomFactory.randomNumberBetween(0, collectionClasses.length);
		Class<?> collectionClass = collectionClasses[index];

		Constructor<?>[] constructors = collectionClass.getConstructors();		
		for(Constructor<?> c:constructors){
			if(c.getParameterTypes().length == 0){
				constructor = c;
				break;
			}
		}

		if(constructor == null){
			index = RandomFactory.randomNumberBetween(0, constructors.length);
			constructor = constructors[index];
		}

		return constructor;
	}

	private static boolean isCompoundPrimitive(Class<?> clas){
		boolean isCompound = false;

		if(clas.equals(Integer.class) ||
				clas.equals(Double.class) ||
				clas.equals(Boolean.class) ||
				clas.equals(Float.class) ||
				clas.equals(Long.class) ||
				clas.equals(Short.class)){
			isCompound = true;			
		}

		return isCompound;
	}
}
